﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using Common.Linq;

namespace Common.Linq
{
    public class LinqDictionary<TKey, TValue> : IDictionary<TKey, TValue> where TValue : class
    {
        private IProvider<TValue> m_Source;
        private Expression<Func<TValue, TKey>> m_GetKeyExpr;
        private Func<TValue, TKey> m_GetKey;

        private KeyCollection m_Keys;
        private ValueCollection m_Values;

        public LinqDictionary(IProvider<TValue> source, Expression<Func<TValue, TKey>> getKey)
        {
            this.m_Source = source;
            this.m_GetKeyExpr = getKey;
            this.m_GetKey = this.m_GetKeyExpr.Compile();

            this.m_Keys = new KeyCollection(this);
            this.m_Values = new ValueCollection(this);
        }

        public ICollection<TKey> Keys
        {
            get { return this.m_Keys; }
        }

        public ICollection<TValue> Values
        {
            get { return this.m_Values; }
        }

        public TValue this[TKey key]
        {
            get
            {
                return this.m_Source.Items.First(ByKey(key));
            }

            set
            {
                this.Remove(key);
                this.Add(key, value);
            }
        }

        public Int32 Count
        {
            get { return this.m_Source.Items.Count(); }
        }

        public Boolean ContainsKey(TKey key)
        {
            return this.m_Source.Items.Any(ByKey(key));
        }

        public Boolean TryGetValue(TKey key, out TValue value)
        {
            value = default(TValue);
            if (this.ContainsKey(key))
            {
                value = this[key];
                return true;
            }
            else
            {
                return false;
            }
        }

        public void Add(TKey key, TValue value)
        {
            if (!Object.Equals(key, this.m_GetKey(value)))
            {
                throw new ArgumentException("The key does not match the key returned for the value.", "key");
            }

            this.m_Source.Add(value);
            this.m_Source.ApplyChanges();
        }

        public Boolean Remove(TKey key)
        {
            TValue value;
            if (this.TryGetValue(key, out value))
            {
                this.m_Source.Delete(value);
                this.m_Source.ApplyChanges();
                return true;
            }
            else
            {
                return false;
            }
        }

        public void Clear()
        {
            this.m_Source.Clear();
            this.m_Source.ApplyChanges();
        }

        #region ICollection<KeyValuePair<TKey,TValue>> Members

        void ICollection<KeyValuePair<TKey,TValue>>.Add(KeyValuePair<TKey, TValue> item)
        {
            this.Add(item.Key, item.Value);
        }

        Boolean ICollection<KeyValuePair<TKey,TValue>>.Contains(KeyValuePair<TKey, TValue> item)
        {
            TValue existing;
            return this.TryGetValue(item.Key, out existing) && Object.Equals(existing, item.Value);
        }

        void ICollection<KeyValuePair<TKey,TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, Int32 arrayIndex)
        {
            throw new NotImplementedException();
        }

        Boolean ICollection<KeyValuePair<TKey,TValue>>.IsReadOnly
        {
            get { throw new NotImplementedException(); }
        }

        Boolean ICollection<KeyValuePair<TKey,TValue>>.Remove(KeyValuePair<TKey, TValue> item)
        {
            TValue existing;
            if (this.TryGetValue(item.Key, out existing) && Object.Equals(existing, item.Value))
            {
                return this.Remove(item.Key);
            }
            else
            {
                return false;
            }
        }

        #endregion

        #region IEnumerable<KeyValuePair<TKey,TValue>> Members

        public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
        {
            throw new NotImplementedException();
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        #endregion

        private Expression<Func<TValue, Boolean>> ByKey(TKey key)
        {
            ParameterExpression value = Expression.Parameter(typeof(TValue), "value");

            return Expression.Lambda<Func<TValue, Boolean>>(
                Expression.Equal(Expression.Invoke(this.m_GetKeyExpr, value), Expression.Constant(key)),
                value);
        }

        private class KeyCollection : ICollection<TKey>
        {
            private LinqDictionary<TKey, TValue> m_Parent;

            public KeyCollection(LinqDictionary<TKey, TValue> parent)
            {
                this.m_Parent = parent;
            }

            public Int32 Count
            {
                get { return this.m_Parent.m_Source.Items.Count(); }
            }

            public Boolean IsReadOnly
            {
                get { return true; }
            }

            public Boolean Contains(TKey item)
            {
                return this.m_Parent.m_Source.Items.Any(this.m_Parent.ByKey(item));
            }

            public void CopyTo(TKey[] array, Int32 arrayIndex)
            {
                foreach (TKey key in this.m_Parent.m_Source.Items.Select(this.m_Parent.m_GetKeyExpr))
                {
                    if (arrayIndex >= array.Length)
                    {
                        throw new IndexOutOfRangeException("The Array is Not Larget Enough to Fit the Array.");
                    }

                    array[arrayIndex++] = key;
                }
            }

            #region Not Supported

            public void Add(TKey item)
            {
                throw new NotSupportedException();
            }

            public Boolean Remove(TKey item)
            {
                throw new NotSupportedException();
            }

            public void Clear()
            {
                throw new NotSupportedException();
            }

            #endregion

            #region IEnumerable<TKey> Members

            public IEnumerator<TKey> GetEnumerator()
            {
                foreach (TKey key in this.m_Parent.m_Source.Items.Select(val => this.m_Parent.m_GetKey(val)))
                {
                    yield return key;
                }
            }

            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
            {
                return this.GetEnumerator();
            }

            #endregion
        }

        private class ValueCollection : ICollection<TValue>
        {
            private LinqDictionary<TKey, TValue> m_Parent;

            public ValueCollection(LinqDictionary<TKey, TValue> parent)
            {
                this.m_Parent = parent;
            }

            public Int32 Count
            {
                get { return this.m_Parent.m_Source.Items.Count(); }
            }

            public Boolean IsReadOnly
            {
                get { return true; }
            }

            public Boolean Contains(TValue item)
            {
                return this.m_Parent.m_Source.Items.Any(val => Object.Equals(val, item));
            }

            public void CopyTo(TValue[] array, Int32 arrayIndex)
            {
                foreach (TValue value in this.m_Parent.m_Source.Items)
                {
                    if (arrayIndex >= array.Length)
                    {
                        throw new IndexOutOfRangeException("The Array is Not Larget Enough to Fit the Array.");
                    }

                    array[arrayIndex++] = value;
                }
            }

            #region Not Supported

            public void Add(TValue item)
            {
                throw new NotSupportedException();
            }

            public Boolean Remove(TValue item)
            {
                throw new NotSupportedException();
            }

            public void Clear()
            {
                throw new NotSupportedException();
            }

            #endregion

            #region IEnumerable<TValue> Members

            public IEnumerator<TValue> GetEnumerator()
            {
                return this.m_Parent.m_Source.Items.GetEnumerator();
            }

            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
            {
                return this.GetEnumerator();
            }

            #endregion
        }
    }
}
